﻿using System;
using System.Data;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Configuration;

namespace Nasa8x.Linq
{
    public partial class BaseDataContext : DataContext, IDisposable
    {

        public static String ConnectionString
        {
            get
            {

                return ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
            }
        }

        protected bool IsDisposed { get; private set; }
        private IsolationLevel TransactionIsolationLevel { get; set; }
        partial void OnCreated();
        partial void OnTransactionCreated();
        #region Constructors

        public BaseDataContext(string connectionString, MappingSource mapping) : this(connectionString, IsolationLevel.ReadUncommitted, mapping) { }
        public BaseDataContext(IDbConnection connection) : this(connection.ConnectionString) { }
        public BaseDataContext(IDbConnection connection, MappingSource mapping) : this(connection.ConnectionString, IsolationLevel.ReadUncommitted, mapping) { }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="connectionString">The connection string to connect to the database.</param>
        public BaseDataContext(string connectionString)
            : this(connectionString, IsolationLevel.ReadUncommitted)
        {
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="connectionString">The connection string to connect to the database.</param>
        /// <param name="noLock">If performing READ-ONLY operations, set to true. If 
        /// a transaction is required to ensure data-integrity, set to false.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown when the specified <paramref name="connectionString"/> is <see langword="null"/>.
        /// </exception>
        public BaseDataContext(string connectionString, bool noLock)
            : this(connectionString, noLock == true ? System.Data.IsolationLevel.ReadUncommitted : System.Data.IsolationLevel.ReadCommitted)
        {

        }

        public BaseDataContext(string connectionString, IsolationLevel level, MappingSource mapping)
            : base(connectionString, mapping)
        {
            if (string.IsNullOrEmpty(connectionString))
            {
                throw new ArgumentNullException("connectionString");
            }
            TransactionIsolationLevel = level;
            Connection.StateChange += OnConnectionStateChange;
            OnCreated();
        }

        /// <summary>
        /// Handles the Begin / End Transaction Logic
        /// 
        /// This should make the Context less eager to open and ONLY
        /// start a trans when needed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnConnectionStateChange(object sender, StateChangeEventArgs e)
        {
            if (e.CurrentState == ConnectionState.Open && e.OriginalState == ConnectionState.Closed)
            {
                BeginTransaction(TransactionIsolationLevel);

            }
            if (e.CurrentState == ConnectionState.Closed && e.OriginalState == ConnectionState.Open)
            {
                CommitTransaction();
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="connectionString">The connection string to connect to the database.</param>
        /// <param name="level">The isolation level for the current transaction. <see cref="IsolationLevel"/></param>
        public BaseDataContext(string connectionString, IsolationLevel level)
            : this(connectionString, level, new AttributeMappingSource())
        {
        }
        #endregion
        /// <summary>
        /// Closes the internal context connection
        /// </summary>
        private void CloseConnection()
        {
            if (!IsDisposed && Connection.State == ConnectionState.Open)
            {
                Connection.Close();
            }
        }
        /// <summary>
        /// Open the internal context connection
        /// </summary>
        private void OpenConnection()
        {
            if (!IsDisposed && Connection.State == ConnectionState.Closed)
            {
                Connection.Open();
            }

        }

        /// <summary>
        /// Starts a new transaction for the current DataContext.
        /// Ensures that the connection is open prior to creating an open
        /// connection.
        /// </summary>
        /// <param name="isolationLevel"><see cref="IsolationLevel"/></param>
        private void BeginTransaction(IsolationLevel isolationLevel)
        {
            OpenConnection();
            if (this.Transaction == null)
            {
                this.Transaction = this.Connection.BeginTransaction(isolationLevel);
                OnTransactionCreated();
            }
        }
        /// <summary>
        /// Commits the transaction
        /// </summary>
        /// <param name="trans"></param>
        private void CommitTransaction()
        {
            if (!IsDisposed && this.Transaction != null && this.Transaction.Connection != null)
            {
                try
                {
                    this.Transaction.Commit();
                }
                catch
                {
                    this.Transaction.Rollback();
                    throw;
                }
                finally
                {
                    this.Transaction.Dispose();
                    this.Transaction = null;
                }
            }
        }


        private void EnsureTransaction()
        {
            if (this.Transaction == null)
            {
                BeginTransaction(TransactionIsolationLevel);
            }
        }
        /// <summary>
        /// Submits all of the changes for this DataContext into the database.
        /// 
        /// WARNING: Your changes are NOT saved to the database unless Submit
        /// is called prior to disposing.
        /// <exception cref="T:System.Exception">
        /// Thrown when the specified changes cause a DB exception.
        /// </exception>
        /// </summary>
        public new void SubmitChanges()
        {
            this.SubmitChanges(ConflictMode.FailOnFirstConflict);

        }
        public new void SubmitChanges(ConflictMode failureMode)
        {
            try
            {
                EnsureTransaction();
                base.SubmitChanges(failureMode);
                CommitTransaction();
                CloseConnection();
            }
            catch
            {
                throw;
            }

        }

        #region IDisposable Members

        /// <summary>
        /// Verify the object has been disposed
        /// </summary>
        protected void CheckDisposed()
        {
            if (IsDisposed)
            {
                throw new ObjectDisposedException("BaseDataContext");
            }
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing"></param>
        public void Dispose(bool disposing)
        {
            if (!IsDisposed && disposing)
            {
                CommitTransaction();
                CloseConnection();
                base.Dispose();
                IsDisposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);

            // Suppress finalization of this disposed instance.
            GC.SuppressFinalize(this);
        }

        ~BaseDataContext()
        {
            Dispose(false);
        }

        #endregion
    }
}
